<script lang="ts">
  import { Send } from "lucide-svelte";
  import { drawStore } from "../store.svelte";
  import { getPromptText } from "../nodes/types.svelte";
  import {
    chat_completion,
    list_llm_model,
    type ChatChunkResponse,
    type ChatMsg,
    type ChatMsgChunkData,
  } from "$lib/api/ai_provider/llm";
  import type { LlmInfo } from "$lib/api/board/board";
  import { request } from "$lib/utils/request";
  import { get_user_session } from "$lib/api/user";
  import {
    create_edge,
    create_node,
    EDGE_TYPE_SMOOTH_STEP,
    update_node_content,
    type DrawEdgeInfo,
    type ExDrawNodeInfo,
  } from "$lib/api/board/board_draw";
  import { unSelectNode } from "../DrawFlowHelp.svelte";
  import { Channel } from "@tauri-apps/api/core";
  import dayjs from "dayjs";
  import type { XYPosition } from "@xyflow/svelte";

  let askContent = $state(prepareAskContent());
  let curLlmInfo = $state(null as null | LlmInfo);

  let modelNameList = $state([] as string[]);
  let curModelName = $state("");

  function prepareAskContent() {
    if (drawStore.selectNodeIdList.length == 0) {
      return "";
    }
    const curNodeId = drawStore.selectNodeIdList[0];
    const node = drawStore.nodeList.find((item) => item.node_id == curNodeId);
    if (node == undefined) {
      return "";
    }
    return node.content.LlmAskContent?.content ?? "";
  }

  async function loadModelNameList() {
    if (curLlmInfo == null) {
      return;
    }
    const res = await list_llm_model(
      curLlmInfo.basic_info.api_addr,
      curLlmInfo.basic_info.api_key,
    );
    if (Array.isArray(res)) {
      modelNameList = res.map((item) => item.id);
      if (modelNameList.includes(curLlmInfo.basic_info.default_model_name)) {
        curModelName = curLlmInfo.basic_info.default_model_name;
      } else if (modelNameList.length > 0) {
        curModelName = modelNameList[0];
      } else {
        curModelName = "";
      }
    }
  }

  function getParentEdgeList(nodeId: string): DrawEdgeInfo[] {
    return drawStore.edgeList.filter(
      (item) => item.edge_key.to_node_id == nodeId,
    );
  }

  function getParentNodeIdList(nodeId: string): string[] {
    return drawStore.edgeList.filter(
      (item) => item.edge_key.to_node_id == nodeId,
    ).map(item=>item.edge_key.from_node_id);
  }

  //向上递归，直到llm start
  async function prepareChatMsg(
    chatMsgList: ChatMsg[],
    nodeId: string,
    allNodeIdList: string[],
  ) {
    if (allNodeIdList.includes(nodeId)) {
      return;
    }
    allNodeIdList.push(nodeId);
    //加入当前节点
    const node = drawStore.nodeList.find((item) => item.node_id == nodeId);
    if (node == undefined) {
      return;
    }
    if (node.content.LlmAskContent != undefined) {
      const tmpTextList: string[] = [];
      //获取父节点
      const parentNodeIdList = getParentNodeIdList(nodeId);
      const parentNodeList = drawStore.nodeList.filter((item) =>
        parentNodeIdList.includes(item.node_id)
      );
      //处理非llm父节点
      const parentEdgeList = getParentEdgeList(nodeId);
      for (const parentEdge of parentEdgeList) {
        const parentNode = drawStore.nodeList.find(
          (item) => item.node_id == parentEdge.edge_key.from_node_id,
        );
        if (
          parentNode == undefined ||
          parentNode.content.LlmAskContent != undefined ||
          parentNode.content.LlmAnswerContent != undefined
        ) {
          continue;
        }
        const promptText = await getPromptText(
          parentNode.content,
          parentEdge.edge_key.from_handle,
        );
        tmpTextList.push(promptText);
      }
      let userAskContent = "";
      if (
        drawStore.selectNodeIdList.length == 1 &&
        drawStore.selectNodeIdList[0] == nodeId
      ) {
        userAskContent = askContent;
      } else {
        userAskContent = node.content.LlmAskContent.content ?? "";
      }
      //加入当前节点内容
      if (tmpTextList.length > 0) {
        chatMsgList.unshift({
          role: "user",
          content: `
 请根据下面资料回答问题:
 
 # 资料
 ${tmpTextList.join("\n\n")}

 # 问题

 ${userAskContent}
        `,
        });
      } else {
        chatMsgList.unshift({
          role: "user",
          content: userAskContent,
        });
      }

      //处理llm父母节点
      for (const parentNode of parentNodeList) {
        if (parentNode.content.LlmAnswerContent != undefined) {
          await prepareChatMsg(chatMsgList, parentNode.node_id, allNodeIdList);
        }
      }
    } else if (node.content.LlmAnswerContent != undefined) {
      if (node.content.LlmAnswerContent.content.trim() != "") {
        chatMsgList.unshift({
          role: "assistant",
          content: node.content.LlmAnswerContent.content,
        });
      }
      //获取父节点
      const parentNodeIdList = getParentNodeIdList(nodeId);
      for (const parentNodeId of parentNodeIdList) {
        await prepareChatMsg(chatMsgList, parentNodeId, allNodeIdList);
      }
    }
  }

  function findAnswerPosition(curNode: ExDrawNodeInfo): XYPosition {
    const pos = {
      x: curNode.x,
      y: curNode.y + curNode.h + 200,
    };
    //列出子节点
    const childNodeIdList = drawStore.edgeList
      .filter((item) => item.edge_key.from_node_id == curNode.node_id)
      .map((item) => item.edge_key.to_node_id);

    const childNodeList = drawStore.nodeList.filter((item) =>
      childNodeIdList.includes(item.node_id),
    );
    for (const childNode of childNodeList) {
      const newX = childNode.x + childNode.w + 200;
      if (newX > pos.x) {
        pos.x = newX;
      }
    }
    return pos;
  }

  async function processAnswer(
    sessionId: string,
    curNode: ExDrawNodeInfo,
    llmInfo: LlmInfo,
    modelName: string,
    chatMsgList: ChatMsg[],
  ) {
    //创建答案节点
    const pos = findAnswerPosition(curNode);
    const nodeRes = await request(
      create_node({
        session_id: sessionId,
        board_id: drawStore.boardInfo?.board_id ?? "",
        draw_id: drawStore.drawId,
        x: pos.x,
        y: pos.y,
        w: 400,
        h: 200,
        content: {
          LlmAnswerContent: {
            content: "",
            provider_name: llmInfo.basic_info.name,
            model_name: modelName,
            done: false,
          },
        },
        node_style: {
          bg_color: "bg-surface-200",
          border_width: "border-1",
          border_color: "bg-surface-200",
          font_color: "text-primary-900",
          layer: 10,
        },
      }),
    );
    //创建问题和答案连接
    await request(
      create_edge({
        session_id: sessionId,
        board_id: drawStore.boardInfo?.board_id ?? "",
        draw_id: drawStore.drawId,
        edge_key: {
          from_node_id: curNode.node_id,
          from_handle: "b",
          to_node_id: nodeRes.node_id,
          to_handle: "t",
        },
        basic_info: {
          edge_type: EDGE_TYPE_SMOOTH_STEP,
          stroke_width: 3,
          color: "var(--color-secondary-900)",
          has_start_arrow: false,
          has_end_arrow: true,
          label: "",
          llm_link: true,
        },
      }),
    );
    //更新答案节点
    let allAnswerContent = "";
    let lastUpdateTime = 0;
    const onEvent = new Channel<ChatMsgChunkData>();
    onEvent.onmessage = (chunkMsg) => {
      if (!chunkMsg.success) {
        console.log(chunkMsg.value);
        return;
      }
      //处理chunk数据
      const lines = chunkMsg.value.split("\n");
      for (let line of lines) {
        const pos = line.indexOf("{");
        if (pos == -1) {
          continue;
        }
        const jsonStr = line.substring(pos);
        const obj = JSON.parse(jsonStr);
        if (obj.error == undefined) {
          (obj as ChatChunkResponse).choices.forEach((item) => {
            if (typeof item.delta.content == "string") {
              allAnswerContent += item.delta.content;
            } else if (item.delta.content.type == "text") {
              allAnswerContent += item.delta.content.text;
            }
          });
        }
      }

      const now = dayjs().valueOf();
      if (now - lastUpdateTime < 1000) {
        // 1秒最多更新一次
        return;
      }
      if (
        drawStore.nodeList.findIndex(
          (item) => item.node_id == nodeRes.node_id,
        ) == -1
      ) {
        return;
      }
      lastUpdateTime = now;
      request(
        update_node_content({
          session_id: sessionId,
          board_id: drawStore.boardInfo?.board_id ?? "",
          draw_id: drawStore.drawId,
          node_id: nodeRes.node_id,
          content: {
            LlmAnswerContent: {
              content: allAnswerContent,
              provider_name: llmInfo.basic_info.name,
              model_name: modelName,
              done: false,
            },
          },
        }),
      );
    };
    await chat_completion(
      llmInfo.basic_info.api_addr,
      llmInfo.basic_info.api_key,
      modelName,
      chatMsgList,
      onEvent,
    );
    if (
      drawStore.nodeList.findIndex((item) => item.node_id == nodeRes.node_id) ==
      -1
    ) {
      return;
    }
    //保证答案完整
    await request(
      update_node_content({
        session_id: sessionId,
        board_id: drawStore.boardInfo?.board_id ?? "",
        draw_id: drawStore.drawId,
        node_id: nodeRes.node_id,
        content: {
          LlmAnswerContent: {
            content: allAnswerContent,
            provider_name: llmInfo.basic_info.name,
            model_name: modelName,
            done: true,
          },
        },
      }),
    );
  }

  async function sendAsk() {
    if (drawStore.selectNodeIdList.length == 0) {
      return;
    }
    if (curLlmInfo == null || curModelName == "") {
      return;
    }
    const curNodeId = drawStore.selectNodeIdList[0];
    const curNode = drawStore.nodeList.find(
      (item) => item.node_id == curNodeId,
    );
    if (curNode == undefined) {
      return;
    }
    unSelectNode(curNodeId);
    const allNodeIdList: string[] = []; //防止死循环
    const chatMsgList: ChatMsg[] = [];
    await prepareChatMsg(chatMsgList, curNodeId, allNodeIdList);
    //加入系统提示词
    chatMsgList.unshift({
      role: "system",
      content:
        curLlmInfo.basic_info.system_prompt == ""
          ? "你是一个有用的助理"
          : curLlmInfo.basic_info.system_prompt,
    });

    const sessionId = await get_user_session();
    //更新问题节点
    await request(
      update_node_content({
        session_id: sessionId,
        board_id: drawStore.boardInfo?.board_id ?? "",
        draw_id: drawStore.drawId,
        node_id: curNodeId,
        content: {
          LlmAskContent: {
            content: askContent,
          },
        },
      }),
    );
    //处理答案
    await processAnswer(
      sessionId,
      curNode,
      curLlmInfo,
      curModelName,
      chatMsgList,
    );
  }

  $effect(() => {
    if (
      drawStore.boardInfo != null &&
      drawStore.boardInfo.llm_list.length > 0 &&
      curLlmInfo == null
    ) {
      curLlmInfo = drawStore.boardInfo.llm_list[0];
      loadModelNameList();
    }
  });
</script>

<div class="ml-[300px] flex flex-wrap w-[calc(100vw-600px)] mb-2 gap-2">
  <label
    class="label flex gap-2 items-center justify-center bg-surface-200-800 p-1 rounded-[10px] w-[330px]"
  >
    <div class="label-text text-xl">模型</div>
    <select
      class="select p-0 w-[100px]"
      value={curLlmInfo?.llm_id ?? ""}
      onchange={(e) => {
        const newLlmInfo = (drawStore.boardInfo?.llm_list ?? []).find(
          (item) => item.llm_id == e.currentTarget.value,
        );
        if (newLlmInfo != undefined) {
          curLlmInfo = newLlmInfo;
          loadModelNameList();
        } else {
          curLlmInfo = null;
        }
      }}
    >
      {#each drawStore.boardInfo?.llm_list ?? [] as llm}
        <option value={llm.llm_id}>
          {llm.basic_info.name}
        </option>
      {/each}
    </select>
    <select class="select p-0 w-[150px]" bind:value={curModelName}>
      {#each modelNameList as modelName}
        <option value={modelName}>{modelName}</option>
      {/each}
    </select>
  </label>
</div>
<div
  class="w-[calc(100vw-500px)] border-surface-300 ml-[300px] flex ring-1 ring-surface-200"
>
  <textarea
    class="textarea bg-surface-100-900 flex-1 ring-0"
    rows="2"
    placeholder="请输入问题"
    bind:value={askContent}
  ></textarea>
  <div class="w-[60px] flex items-center justify-center gap-1 mr-[20px]">
    <button
      type="button"
      class="btn {askContent.trim() == ''
        ? ''
        : 'preset-filled-primary-900-100'}"
      title="发送问题"
      disabled={askContent.trim() == "" ||
        curLlmInfo == null ||
        curModelName == ""}
      onclick={() => sendAsk()}
    >
      <Send />
    </button>
  </div>
</div>
